#ifndef BUFFEREDOUTPUTBOX_H_JM_20121107
#define BUFFEREDOUTPUTBOX_H_JM_20121107
/** @file BufferedOutputBox.h
 * _OK_ Rozsirenie chovania boxov o bufferovany vystup.
 **/

#include "RootBox.h"
#include "BufferedOutputBoxImpl.h"
#include "../LogConf.h"
namespace jm4bbx {

	class BaseBufferedOutputBox : public RootBox {
	public:
		static size_t DEFAULT_CHUNK_SIZE;
		BaseBufferedOutputBox(const BoxParamPack& p) : RootBox(p) { } 

	};


	///**
	// * BufferedOutputBox rozsiruje chovanie boxov o bufferovanie vystupnych dat.
	// * Odvodene boxy sa nemusia starat o pracu s pamatov a postupnym odosielanim
	// * dat. Data staci len vkladata do vystupu {@link addOutputData()} a podla 
	// * potreby posielat dalej {@link sendOutputData()}. Pre riadenie odosielania 
	// * je mozne pouzit {@link isOutputEmpty()} - detekcia ci je co posielat a
	// * {@link isOutputFull()} - detekcia optimalnosti odoslania.
	// * 
	// * Vystup je obmedzeny na jednu skupinu s jednym stlpcom.
	// * 
	// * @tparam policyT typ politiky vystupnych dat
	// * @tparam valueT typ vystupnych dat
	// **/
	//template<typename policyT, typename valueT>
	//class BufferedOutputBox : public BaseBufferedOutputBox { 
	//public:
	//	JM_USE_LOGGER(boxes_bufferedOutputBox_logger)
	//	/// Policy vystupneho typu.
	//	typedef policyT boxOutputPolicyType;

	//	/// Vystupny typ.
	//	typedef valueT boxOutputType;

	//	/// Meno parametra, ktory urcuje {@link BufferedOutputBox::RESULT_CHUNK_SIZE}.
	//	static const char* RESULT_CHUNK_SIZE_PARAMETER_NAME;

	//
	//	BufferedOutputBox(const BoxParamPack& p, const groupType& g, 
	//		const columnType& c) 
	//		: BaseBufferedOutputBox(p)
	//		, OUTPUT_GROUP(g)
	//		, OUTPUT_COLUMN(c)
	//		, RESULT_CHUNK_SIZE(DEFAULT_CHUNK_SIZE)/*getBoxParameterValue(p, 
	//			RESULT_CHUNK_SIZE_PARAMETER_NAME, DEFAULT_CHUNK_SIZE))*/
	//		, messageRecordCounter_(0) {

	//			LOG_I("RESULT_CHUNK_SIZE == " << RESULT_CHUNK_SIZE)
	//	}		

	//
	//	/// Je vystup resp. vystupna sprava naplnena?  
	//	/// Data sa posielaju v spravach, ktore maju pevnu kapacitu 
	//	/// {@link RESULT_CHUNK_SIZE}. Pokial je sprava naplnena (tj. metoda 
	//	/// vrati true) tak sa dalsie vkladane data budu ukaldat do bufferu.
	//	/// Pre najoptimalnejsie fungovanie je vhodne odosielat spravy 
	//	/// {@link sendOutputData()} az po naplneni spravy. Metoda detekuje 
	//	/// len naplnenie vystupu ale nekvatifikuje ho, tj. po osolani 
	//	/// spravy moze byt vystup stale plny pokial pocet dat na vystupe 
	//	/// presahuje dvojnasobok velkosti spravy.
	//	inline bool isOutputFull() const {
	//		return RESULT_CHUNK_SIZE <= messageRecordCounter_ + outputBuffer_.size();
	//	}

	//	/// Su na vystupe cakajuce data na odoslanie?
	//	/// Reflektuje vsetky data buffer + sprava.
	//	inline bool isOutputEmpty() {
	//		return messageRecordCounter_ + outputBuffer_.size() <= 0;
	//	}
	//	/// Pridaj vysledok na vystup.
	//	void addOutputData(const boxOutputType& result) {
	//		if (isOutputMessageFull()) {
	//			// Pokial je sprava plna -> treba data bufferovat.
	//			outputBuffer_.push_back(result);			
	//		}
	//		else {
	//			addData2OutputMessage(result);
	//		}
	//	}
	//
	//	/// Odosli data.
	//	bool sendOutputData(const outType& outArc) {
	//		if (!isOutputMessageFull()) {
	//			// Presun dat z bufferu pokial je v sprave este volne miesto.
	//			moveBufferedDataToMessage();
	//		}
	//		bool sent = messageRecordCounter_ > 0; // Poslal som vobec nieco?
	//		if (sent) {
	//			// Nastavenie rozsahu dat + fyzicke odoslanie
	//			getGroupPtr(OUTPUT_GROUP)->relocate(0, messageRecordCounter_);
	//			send(outArc, OUTPUT_GROUP);
	//		
	//			// Znulovanie citaca dat v sprave a alokovanie noveho priestoru pre
	//			// vystup.
	//			messageRecordCounter_ = 0;
	//			allocateResultChunk();
	//		}
	//		return sent;
	//	}

	//private:	
	//	/// Vystupna skupina.
	//	const groupType OUTPUT_GROUP;

	//	/// Vystupny stlpec.
	//	const columnType OUTPUT_COLUMN;

	//	/// Buffer pre vysledky, ktore sa nezmestia do vystupnej spravy pocas 
	//	/// jedneho spracovania.
	//	std::vector<boxOutputType> outputBuffer_;

	//	/// Citac poctu zaznamov-vysledkov vlozenych na vystup.
	//	size_t messageRecordCounter_;

	//	/// Maximany mozny pocet dat poslatelnych jednou vystupnou spravou.
	//	/// Prilis male cislo -> vela opakovani -> zbytocna rezia.
	//	/// Prilis velke cislo -> zbytocna alokacia.
	//	/// Nutne toto musi byt konstanta pretoze alokacia pamate je odkladana
	//	/// takze volanie allocate() nesmie byt bezprostredne pred pouzitim 
	//	/// pamate.
	//	const size_t RESULT_CHUNK_SIZE;

	//	virtual void init_impl() override {
	//		RootBox::init_impl();
	//		allocateResultChunk();
	//	}

	//	/// Presun data z buferu do spravy. 
	//	/// Presunie sa maximalny mozny pocet dat tj. pokial nie je sprava plna. 
	//	/// Data, ktore sa do spravy nezmestia zostanu v befere.
	//	void moveBufferedDataToMessage() {
	//		while (!isOutputMessageFull() && !outputBuffer_.empty()) {
	//			addData2OutputMessage(outputBuffer_[outputBuffer_.size() - 1]);
	//			outputBuffer_.pop_back();
	//		}
	//	}

	//	/// Alokovanie noveho priestoru pre vystup.
	//	void allocateResultChunk() {
	//		//allocate(OUTPUT_GROUP, RESULT_CHUNK_SIZE);
	//		getGroupPtr(OUTPUT_GROUP)->allocate(RESULT_CHUNK_SIZE);
	//	}

	//	/// Pridaj data do vystupu - natvrdo bez kontroly volneho miesta.
	//	/// @see isOutputFull()
	//	inline void addData2OutputMessage(const boxOutputType& r) {
	//		assert(!isOutputMessageFull());
	//		get_data<boxOutputPolicyType>(OUTPUT_COLUMN)[messageRecordCounter_++] = r;
	//	}

	//	/// Je vystupna sprava naplnena? 
	//	/// Testuje len pocet dat vo vystupnej sprave tj. nezohladnuje data 
	//	/// vo vystupnom buffere.
	//	/// @see isOutputFull() je urcene pre transparentnu detekciu stavu 
	//	/// vystupu (nerozlisuje bufferovane a nebufferovane data).
	//	/// @return true sprava je plna a uz nepojme dalsie data. 
	//	/// @return false sprava nie je plna a mozno vkladat dalsie data. 
	//	bool isOutputMessageFull() const {
	//		return RESULT_CHUNK_SIZE <= messageRecordCounter_;
	//	}
	//};

	//template<typename policyT, typename valueT>
	//const char* BufferedOutputBox<policyT, valueT>::
	//	RESULT_CHUNK_SIZE_PARAMETER_NAME = "resultChunkSize";	
	//
	// 
template<typename valueT>
class BufferedOutputBox : public BaseBufferedOutputBox { 
public:
	typedef valueT ValueType;
	typedef BufferedOutputBoxImpl<ValueType> ImplType;
	typedef std::vector<ImplType> Outputs;
	typedef size_t OutId;

	/// Meno parametra, ktory urcuje {@link BufferedOutputBox::RESULT_CHUNK_SIZE}.
	static const char* RESULT_CHUNK_SIZE_PARAMETER_NAME;

	BufferedOutputBox(const BoxParamPack& p) 
		: BaseBufferedOutputBox(p)
		, RESULT_CHUNK_SIZE(DEFAULT_CHUNK_SIZE) { }		




	OutId addBufferedOutput(const groupType& g, const columnType& c, 
		const outType& a, const size_t chunkSize = 0) {
			if (chunkSize == 0) {
				outputs_.push_back(ImplType(this, a, g, c, RESULT_CHUNK_SIZE));
			}
			else {
				outputs_.push_back(ImplType(this, a, g, c, chunkSize));
			}
			return outputs_.size() - 1;
	}

	/// Je vystup resp. vystupna sprava naplnena?  
	/// Data sa posielaju v spravach, ktore maju pevnu kapacitu 
	/// {@link RESULT_CHUNK_SIZE}. Pokial je sprava naplnena (tj. metoda 
	/// vrati true) tak sa dalsie vkladane data budu ukaldat do bufferu.
	/// Pre najoptimalnejsie fungovanie je vhodne odosielat spravy 
	/// {@link sendOutputData()} az po naplneni spravy. Metoda detekuje 
	/// len naplnenie vystupu ale nekvatifikuje ho, tj. po osolani 
	/// spravy moze byt vystup stale plny pokial pocet dat na vystupe 
	/// presahuje dvojnasobok velkosti spravy.
	inline bool isOutputFull(const OutId& id) const {
		return outputs_[id].isOutputFull();
	}

	/// Su na vystupe cakajuce data na odoslanie?
	/// Reflektuje vsetky data buffer + sprava.
	inline bool isOutputEmpty(const OutId& id) const {
		return outputs_[id].isOutputEmpty();
	}
	/// Pridaj vysledok na vystup.
	void addOutputData(const OutId& id, const ValueType& result) {
		return outputs_[id].addOutputData(result);
	}

	/// Odosli data.
	bool sendOutputData(const OutId& id) {
		return outputs_[id].sendOutputData();
	}

	bool sendAllOutputData() {
		bool sent = false;
		for (auto it = outputs_.begin(); it != outputs_.end(); ++it) {
			bool sentLocal = it->sendOutputData();
			sent = sent || sentLocal;
		}
		return sent;
	}


	bool areAllOutputsEmpty() const {
		for (auto it = outputs_.begin(); it != outputs_.end(); ++it) {
			if (!it->isOutputEmpty()) {
				return false;
			}
		}
		return true;
	}

	void initBufferedOutput(const OutId& id) {
		outputs_[id].initBufferedOutput();
	}

	void initBufferedOutputs() {
		for (auto it = outputs_.begin(); it != outputs_.end(); ++it) {
			it->initBufferedOutput();
		}
	}


protected:
	/// Maximany mozny pocet dat poslatelnych jednou vystupnou spravou.
	/// Prilis male cislo -> vela opakovani -> zbytocna rezia.
	/// Prilis velke cislo -> zbytocna alokacia.
	/// Nutne toto musi byt konstanta pretoze alokacia pamate je odkladana
	/// takze volanie allocate() nesmie byt bezprostredne pred pouzitim 
	/// pamate.
	const size_t RESULT_CHUNK_SIZE;

private:	

	Outputs outputs_;
};

template<typename valueT>
const char* BufferedOutputBox<valueT>::RESULT_CHUNK_SIZE_PARAMETER_NAME = "resultChunkSize";	


template<typename valueT>
class BufferedOneOutputBox : public BufferedOutputBox<valueT> { 
public:

	BufferedOneOutputBox(const BoxParamPack& p, const groupType& g, 
		const columnType& c, const outType& a) 
		: BufferedOutputBox(p) {
			id_ = addBufferedOutput(g, c, a, RESULT_CHUNK_SIZE);
	}		

	inline bool isOutputFull() const {
		return BufferedOutputBox::isOutputFull(id_);
	}

	inline bool isOutputEmpty() {
		return BufferedOutputBox::isOutputEmpty(id_);
	}
	void addOutputData(const ValueType& result) {
		return BufferedOutputBox::addOutputData(id_, result);
	}

	/// Odosli data.
	bool sendOutputData() {
		return BufferedOutputBox::sendOutputData(id_);
	}

private:
	OutId id_;
};

} // namespace jm4bbx

#endif
